﻿using ZFY.Core;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Metadata;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using ZFY.DDD.Domain.Repositories;
using ZFY.AspNetCore;
using ZFY.DDD.Domain.Entities.AggregateRoot;

namespace ZFY.EntityFrameworkCore
{
    public abstract class EFRepository<TEntity,TKey, TDbContext> : EFRepository<TEntity, TDbContext>, IBaseRepository<TEntity,TKey>, IScopeDependency, IHasPropertiesAutowired where TDbContext : DbContext where TEntity : class, IEntity<TKey>
    {
        public virtual async Task<TEntity> GetByIdIncludeNavAsync(TKey id)
        {
            var iQueryable = await  IncludeNavPropertyAsync();

            return await iQueryable.SingleOrDefaultAsync(CreateEqualityExpressionForId(id));


        }
        private async Task<IQueryable<TEntity>> IncludeNavPropertyAsync()
        {
            var dbContext = await GetDbContextAsync();
            var query = dbContext.Set<TEntity>().AsQueryable();
            foreach (var nav in dbContext.Model.FindEntityType(typeof(TEntity)).GetNavigations())
            {
                query = query.Include(CreateNav(nav));
            }
            return query;

        }

        private Expression<Func<TEntity, bool>> CreateEqualityExpressionForId(TKey id)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));

            var leftExpression = Expression.PropertyOrField(lambdaParam, "Id");

            var idValue = Convert.ChangeType(id, typeof(TKey));

            Expression<Func<object>> closure = () => idValue;
            var rightExpression = Expression.Convert(closure.Body, leftExpression.Type);

            var lambdaBody = Expression.Equal(leftExpression, rightExpression);

            return Expression.Lambda<Func<TEntity, bool>>(lambdaBody, lambdaParam);
        }


    }

    public abstract class EFRepository<TEntity,TDbContext>: IBaseRepository<TEntity>, IScopeDependency,IHasPropertiesAutowired where TDbContext: DbContext where TEntity:class
    {

        public IZFYServiceProvider ZFYServiceProvider { get; set; }

        protected  IDbContextProvider<TDbContext> DbContextProvider =>ZFYServiceProvider.LazyGetRequiredService<IDbContextProvider<TDbContext>>();

        protected virtual async Task<TDbContext> GetDbContextAsync() { return await DbContextProvider.GetDbContextAsync(); }

        public EFRepository()
        {
            
        }
        public virtual  async Task AddAsync(TEntity entity)
        {
            var dbContext = await GetDbContextAsync();

            await dbContext.AddAsync(entity);
        }

        public virtual async Task AddRangeAsync(List<TEntity> entities)
        {
            var dbContext = await GetDbContextAsync();

            await dbContext.AddRangeAsync(entities);
        }

        public virtual async Task UpdateAsync(TEntity entity)
        {
            var dbContext = await GetDbContextAsync();

            await Task.FromResult(dbContext.Set<TEntity>().Update(entity));
        }

        public virtual async Task UpdateRangeAsync(List<TEntity> entities)
        {
            var dbContext = await GetDbContextAsync();

            dbContext.Set<TEntity>().UpdateRange(entities);
        }


        public virtual async Task DeleteAsync(TEntity entity)
        {
            var dbContext = await GetDbContextAsync();

            dbContext.Set<TEntity>().Remove(entity);
        }
        public virtual async Task DeleteAsync(int key)
        {
            var entity = await FindAsync(key);
            if (entity != null)
            {
                await DeleteAsync(entity);
                return;
            }
        }
        public virtual async Task DeleteRangeAsync(List<TEntity> entities)
        {
            var dbContext = await GetDbContextAsync();

            dbContext.Set<TEntity>().RemoveRange(entities);
        }

        public virtual async Task<TEntity> SingleAsync(Expression<Func<TEntity, bool>> expression)
        {
            var iQueryable = await IncludeNavPropertyAsync();

            return await iQueryable.SingleOrDefaultAsync(expression);
        }
        public virtual async Task<TEntity> SingleIncludeNavAsync(Expression<Func<TEntity, bool>> expression)
        {
            var iQueryable = await IncludeNavPropertyAsync();

            return await iQueryable.SingleOrDefaultAsync(expression);
        }
        private  async Task<IQueryable<TEntity>> IncludeNavPropertyAsync()
        {
            var dbContext = await GetDbContextAsync();
            var query = dbContext.Set<TEntity>().AsQueryable();
            foreach (var nav in dbContext.Model.FindEntityType(typeof(TEntity)).GetNavigations())
            {
                query = query.Include(CreateNav(nav));
            }
            return query;
        }
        public Expression<Func<TEntity, object>> CreateNav(INavigation nav)
        {
            var lambdaParam = Expression.Parameter(typeof(TEntity));

            var body = Expression.PropertyOrField(lambdaParam, nav.Name);
            //body = IncludeChildNav(nav, body, typeof(T));
            return Expression.Lambda<Func<TEntity, object>>(body, lambdaParam);
        }
        public virtual async Task<IQueryable<TEntity>> GetQueryableAsync()
        {
            var queryable = await GetDbSetAsync();

            return queryable.AsQueryable();
        }
        protected async Task<DbSet<TEntity>> GetDbSetAsync()
        {
            return (await GetDbContextAsync()).Set<TEntity>();
        }

        public virtual async Task<IQueryable<TEntity>> WhereAsync(Expression<Func<TEntity, bool>> expression)
        {
            var queryable = await GetQueryableAsync();

            return queryable.Where(expression);
        }

        public virtual async Task<bool> AnyAsync(Expression<Func<TEntity, bool>> expression)
        {
            var queryable = await GetQueryableAsync();

            return await queryable.AnyAsync(expression);
        }

        public virtual async Task<TEntity> FindAsync(int key)
        {
            var dbContext = await GetDbContextAsync();

            return await dbContext.Set<TEntity>().FindAsync(key);
        }
        public virtual async Task<int> SaveChangesAsync()
        {
            var dbContext = await GetDbContextAsync();

            return await dbContext.SaveChangesAsync();
        }
       

    }
}
